/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#include "cgmaterial.h"
#include "archivemanager.h"
#include "exception.h"
#include "rendersystem.h"
#include "resourcemanager.h"
#include "texture.h"

#ifdef _DEBUG
#ifdef _MSC_VER
   #define new new( _CLIENT_BLOCK, __FILE__, __LINE__)
#endif
#endif // _DEBUG

namespace Engine
{
	/*******************************************************************************/
	CGMaterialFactory::CGMaterialFactory()
		:m_type("Material"),m_stateManager(new CGStateManager)
	{}
	/*******************************************************************************/
	CGMaterial* CGMaterialFactory::CreateInstance( const std::string& name )
	{
		return new CGMaterial(name);
	}
	/*******************************************************************************/
	CGMaterial::CGMaterial(const std::string& name)
		:Material(name)
		,m_effect(0)
		,m_isTransparent(false)
	{

	}
	/*******************************************************************************/
	CGMaterial::~CGMaterial()
	{
		UnLoad();
	}
	/*******************************************************************************/
	bool CGMaterial::IsTransparent()const
	{
		return m_isTransparent;
	}
	/*******************************************************************************/
	const TechniquePtr CGMaterial::GetBestTechnique() const
	{
		return m_bestTechnique;
	}
	/*******************************************************************************/
	void CGMaterial::Load()
	{
		if(IsLoaded())
			return;

		RefPtr<DataStream> file = ArchiveManager::GetSingleton().ReadFile(GetName());
		if(!file.IsNull())
		{

			size_t len = file->GetSize();
			RefPtr<char,NoWeakPtrExtension,ArrayDelete> buf(new char[len + 1]);
			len = file->Read(buf.GetPointer(),len);
			buf.GetPointer()[len-1] = 0;

			m_effect = cgCreateEffect(CGStateManager::GetSingleton().GetContext(),buf.GetPointer(), NULL);
			if (!m_effect)
			{
				std::string msg("Unable to create cg effect ");
				const char *listing = cgGetLastListing(CGStateManager::GetSingleton().GetContext());
				if (listing)
					msg += listing;
				throw std::logic_error(msg);
			}
			CGtechnique technique = cgGetFirstTechnique(m_effect);
			while (technique && cgValidateTechnique(technique) == CG_FALSE)
				technique = cgGetNextTechnique(technique);

			if(technique)
			{
				//validate transparent materials
				std::string alphaStateName1("alpha_rejection");
				std::string alphaStateName2("scene_blend");
				CGpass pass = cgGetFirstPass(technique);
				while (pass)
				{

					CGstateassignment sa = cgGetFirstStateAssignment(pass);
					while(sa)
					{
						CGstate st = cgGetStateAssignmentState(sa);
						const char* stname = cgGetStateName(st);
						if(alphaStateName1 == stname || alphaStateName2 == stname)
							m_isTransparent = true;
						sa = cgGetNextStateAssignment(sa);
					}

					pass = cgGetNextPass(pass);
				}
				m_bestTechnique  = RefPtr<CGTechnique,WeakPtrExtension>(new CGTechnique(cgGetTechniqueName(technique), technique, this));
			}

			if(m_bestTechnique.IsNull())
				throw std::invalid_argument("Material " + GetName() + " doesn't contain valid technique for current hardware .CGMaterial::Load");

			//load textures
			CGparameter param = cgGetFirstEffectParameter( m_effect );
			while( param )
			{
				if(CG_TEXTURE == cgGetParameterType(param))
				{
					CGannotation ann = cgGetNamedParameterAnnotation(param,"file");
					const char* file = cgGetStringAnnotationValue(ann);

					TexturePtr tex;
					WeakUtils::UnsafeCopy(tex, ResourceManager::GetSingleton().Create(file,"Texture"));
					tex->Load();

					const char* name = cgGetParameterName(param);
					m_textures[name] = tex;
				}
				param = cgGetNextParameter( param );
			}
			SetLoaded(true);
		}
		else
			throw std::invalid_argument("Can't open material file " + GetName() + " CGMaterial::Load");
	}
	/*******************************************************************************/
	void CGMaterial::UnLoad()
	{
		m_bestTechnique.Delete();

		if(m_effect)
		{
			cgDestroyEffect(m_effect);
			m_effect = 0;
		}

		TexturesMap::iterator i = m_textures.begin(),e = m_textures.end();
		for(;i != e; ++i)
		{
			if(!(i->second).IsNull())
			{
				std::string name = i->second->GetName();
				i->second.Delete();
				ResourceManager::GetSingleton().TryRemove(name);
			}
		}
		SetLoaded(false);
	}
	/*******************************************************************************/
	TexturePtr CGMaterial::GetTexture(const std::string& name)
	{
		if(m_textures.find(name) != m_textures.end())
			return m_textures[name];

		return TexturePtr();
	}
	/*******************************************************************************/

	/*******************************************************************************/
	CGTechnique::CGTechnique(const std::string& name, CGtechnique technique, CGMaterial* material)
		:Technique(name)
		,m_technique(technique)
	{
		CGpass pass = cgGetFirstPass(technique);
		while (pass)
		{
			m_passes.push_back(RefPtr<CGPass, WeakPtrExtension>(new CGPass(pass, material)));
			pass = cgGetNextPass(pass);
		}
	}
	/*******************************************************************************/
	CGTechnique::~CGTechnique()
	{
	}
	/*******************************************************************************/
	unsigned int CGTechnique::GetPassCount() const
	{
		return m_passes.size();
	}
	/*******************************************************************************/
	const PassPtr CGTechnique::GetPass(unsigned int index) const
	{
		if(index < m_passes.size())
			return m_passes[index];
		return PassPtr();
	}
	/*******************************************************************************/
	void CGPass::Begin() const
	{
		CGPassData& data = CGStateManager::GetSingleton().GetPassData(m_pass);
		data.Reset();
		cgSetPassState(m_pass);
		Graphic::GetSingleton().GetRenderSystem()->SetMaterial(data.ambient,data.diffuse,data.emissive,data.specular,data.shininess);

		Graphic::GetSingleton().GetRenderSystem()->SetDepthEnable(data.depth_check);
		Graphic::GetSingleton().GetRenderSystem()->SetDepthWriteEnable(data.depth_write);
		Graphic::GetSingleton().GetRenderSystem()->SetDepthBias(data.depth_bias[0],data.depth_bias[1]);
		Graphic::GetSingleton().GetRenderSystem()->SetDepthBufferFunction(data.depth_func);

		Graphic::GetSingleton().GetRenderSystem()->SetShadeMode(data.shading);
		Graphic::GetSingleton().GetRenderSystem()->SetCullMode(data.cull_mode);
		Graphic::GetSingleton().GetRenderSystem()->SetLightingEnable(data.lighting);
		Graphic::GetSingleton().GetRenderSystem()->SetPolygonMode(data.polygon_mode);

		Graphic::GetSingleton().GetRenderSystem()->SetAlphaRejectSettings(data.alpha_rejection_func,data.alpha_rejection_val);

		Graphic::GetSingleton().GetRenderSystem()->SetSceneBlending(data.scene_blend[0],data.scene_blend[1]);

		Graphic::GetSingleton().GetRenderSystem()->SetFog(data.fog_mode, data.fog_color, data.fog_density, data.fog_start, data.fog_end);

		Graphic::GetSingleton().GetRenderSystem()->SetPointSpritesEnabled(data.point_sprites);
		Graphic::GetSingleton().GetRenderSystem()->SetPointParameters(data.point_size, data.point_size_attenuation_enabled,data.point_size_attenuation[0], data.point_size_attenuation[0], data.point_size_attenuation[0], data.point_size_min, data.point_size_max);

		Graphic::GetSingleton().GetRenderSystem()->SetStencilCheckEnabled(data.stencil);
		Graphic::GetSingleton().GetRenderSystem()->SetStencilBufferParams(data.stencil_func,data.stencil_ref_value,data.stencil_mask,data.stencil_fail,data.stencil_depth_fail,data.stencil_pass);

//textures

		for(int i = 0; i < 8; ++i)
		{
			if(data.texture[i].name.empty())
			{
				Graphic::GetSingleton().GetRenderSystem()->SetTexture(i,TexturePtr());
			}
			else
			{
				TexturePtr tex = m_material->GetTexture(data.texture[i].name);
				if(!tex.IsNull())
					Graphic::GetSingleton().GetRenderSystem()->SetTexture(i,tex);
				else
					throw std::invalid_argument("Texture " + data.texture[i].name + " not loaded.");

				Graphic::GetSingleton().GetRenderSystem()->SetTextureAddressingMode(i,data.texture[i].address_mode);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureBordercolor(i, data.texture[i].border_color);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureUnitFiltering(i,FT_MIN, data.texture[i].min_filter);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureUnitFiltering(i,FT_MAG, data.texture[i].mag_filter);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureUnitFiltering(i,FT_MIP, data.texture[i].mip_filter);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureLayerAnisotropy(i,data.texture[i].anisotropy);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureMipmapBias(i,data.texture[i].bias);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureBlendMode(i, data.texture[i].color_blend_mode);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureBlendMode(i, data.texture[i].alpha_blend_mode);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureMatrix(i,data.texture[i].transform,data.texture[i].tex_transform_coord_dim);
				Graphic::GetSingleton().GetRenderSystem()->SetTextureCoordSet(i, data.texture[i].tex_coord_calc, data.texture[i].tex_coord_calc_index);
			}
		}
	}
	/*******************************************************************************/
	void CGPass::End() const
	{
		cgResetPassState(m_pass);
	}
	/*******************************************************************************/
}
